package com.codedancer.campus.client.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.codedancer.campus.client.service.IUserService;
import com.codedancer.campus.client.service.bo.user.SetBasicMessageBo;
import com.codedancer.campus.client.service.bo.user.UpdatePasswordBo;
import com.codedancer.campus.client.service.bo.user.UpdatePhoneBo;
import com.codedancer.campus.client.service.bo.user.UserFollowPageSearchBo;
import com.codedancer.campus.client.service.bo.user.UserLoginBo;
import com.codedancer.campus.client.service.bo.user.UserUpdateAvatarBo;
import com.codedancer.campus.client.service.bo.user.UserUpdateBackgroundImageBo;
import com.codedancer.campus.client.service.bo.user.UserUpdateInfoBo;
import com.codedancer.campus.client.service.model.SystemMessageMqParamModel;
import com.codedancer.campus.client.service.vo.user.UserFollowVo;
import com.codedancer.campus.client.service.vo.user.UserInfoVo;
import com.codedancer.campus.common.exception.BusinessException;
import com.codedancer.campus.common.model.tx.MobileLocalModel;
import com.codedancer.campus.common.utils.CodecUtils;
import com.codedancer.campus.common.utils.MqSendMessageUtil;
import com.codedancer.campus.common.utils.RedisUtil;
import com.codedancer.campus.common.utils.SecurityUtils;
import com.codedancer.campus.common.dataresource.TianXinData;
import com.codedancer.campus.common.utils.UUIDGenerator;
import com.codedancer.campus.enums.SystemMessageType;
import com.codedancer.campus.enums.UserSourceType;
import com.codedancer.campus.mapper.DynamicMapper;
import com.codedancer.campus.mapper.SchoolMapper;
import com.codedancer.campus.mapper.UserFollowMapper;
import com.codedancer.campus.mapper.UserMapper;
import com.codedancer.campus.mapper.UserQQMapper;
import com.codedancer.campus.mapper.UserSchoolMapper;
import com.codedancer.campus.po.School;
import com.codedancer.campus.po.User;
import com.codedancer.campus.po.UserFollow;
import com.codedancer.campus.po.UserQQ;
import com.codedancer.campus.po.UserSchool;
import com.codedancer.campus.qo.user.UserFollowPageSearchQo;
import com.qq.connect.QQConnectException;
import com.qq.connect.api.OpenID;
import com.qq.connect.api.qzone.UserInfo;
import com.qq.connect.javabeans.AccessToken;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.oauth.Oauth;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private SchoolMapper schoolMapper;
    @Autowired
    private UserSchoolMapper userSchoolMapper;
    @Autowired
    private UserQQMapper userQQMapper;
    @Autowired
    private RedisUtil redisUtil;
    // 注入smsRedis库 smsRedisTemplate
    @Resource(name = "smsRedisTemplate")
    private StringRedisTemplate smsRedisTemplate;
    @Autowired
    private UserFollowMapper userFollowMapper;
    @Autowired
    private DynamicMapper dynamicMapper;
    @Autowired
    private TianXinData tianXinData;
    // 手机号登录验证码key
    @Value("${start.campus.redis.login.phone.key}")
    private String loginCodeKey;
    // 手机号修改验证码key
    @Value("${start.campus.redis.update.phone.key}")
    private String updateCodeKey;
    @Autowired
    private MqSendMessageUtil mqSendMessageUtil;

    /**
     * 用户手机号登录
     *
     * @param userLoginBo 用户登录Bo
     * @return
     */
    @Override
    public String loginByPhone(UserLoginBo userLoginBo)
        throws UnsupportedEncodingException, NoSuchAlgorithmException {
        // 设置redisTemplate
        redisUtil.setRedisTemplate(smsRedisTemplate);
        // 校验验证码
        if (!redisUtil.isExist(loginCodeKey + userLoginBo.getPhone())) {
            throw new BusinessException("验证码已失效");
        }
        String code = redisUtil.getStringValue(loginCodeKey + userLoginBo.getPhone());
        if (!code.equals(userLoginBo.getCode())) {
            throw new BusinessException("验证码错误");
        }
        // 先判断该用户是否已注册
        User user = userMapper.selectOneByColumn("phone", userLoginBo.getPhone());
        if (ObjectUtil.isNotEmpty(user)) {
            // 先判断账号是否被禁用
            if (user.getDisabled()) {
                throw new BusinessException("账号已被禁用，请联系管理员");
            }
            // 直接登录
            return SecurityUtils.genToken(user.getUuid(), user.getPassword());
        }
        // 未注册过
        // 先保存用户信息
        // 设置默认密码为123456
        String salt = CodecUtils.generateSalt();
        user = User.builder()
            .uuid(UUIDGenerator.getUUID())
            .salt(salt)
            .password(CodecUtils.md5Hex(salt, "123456"))
            .sourceType(UserSourceType.PHONE)
            .phone(userLoginBo.getPhone())
            .build();
        // 获取手机号归属地
        MobileLocalModel mobileLocal = tianXinData.getMobileLocal(user.getPhone());
        if (ObjectUtil.isNotEmpty(mobileLocal)) {
            user.setIpaddress(mobileLocal.getMobileprovice() + mobileLocal.getMobilearea());
        }
        userMapper.insert(user);
        // 全部通过删除redis中验证码
        redisUtil.delete(loginCodeKey + userLoginBo.getPhone());
        // 生成token
        return SecurityUtils.genToken(user.getUuid(), user.getPassword());
    }

    /**
     * 获取当前用户信息
     *
     * @param currentLoginUuid 当前用户uuid
     * @return
     */
    @Override
    public UserInfoVo currentUserInfo(String currentLoginUuid) {
        User user = userMapper.findById(currentLoginUuid)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        UserInfoVo userInfoVo = BeanUtil.copyProperties(user, UserInfoVo.class);
        // 获取学校
        UserSchool userSchool = userSchoolMapper.selectOneByColumn("user_uuid", currentLoginUuid);
        if (ObjectUtil.isNotEmpty(userSchool)) {
            School school = schoolMapper.selectById(userSchool.getSchoolUuid());
            userInfoVo.setSchoolName(school.getName());
            userInfoVo.setSchoolUuid(school.getUuid());
        }
        // 获取关注数
        Integer followCount = userFollowMapper.countByColumn("follow_user_uuid", currentLoginUuid);
        userInfoVo.setFollowCount(followCount);
        // 获取被关注数
        Integer followedCount = userFollowMapper
            .countByColumn("followed_user_uuid", currentLoginUuid);
        userInfoVo.setFollowedCount(followedCount);
        // 获取动态数量
        Integer dynamicCount = dynamicMapper.countByColumn("publish_user_uuid", currentLoginUuid);
        userInfoVo.setDynamicCount(dynamicCount);
        // 手机号脱敏
        userInfoVo.setPhone(DesensitizedUtil.mobilePhone(user.getPhone()));
        return userInfoVo;
    }

    /**
     * 修改头像
     *
     * @param userUpdateAvatarBo 修改头像Ro
     */
    @Override
    public void updateAvatar(UserUpdateAvatarBo userUpdateAvatarBo) {
        User user = userMapper.findById(userUpdateAvatarBo.getCurrentUserUuid())
            .orElseThrow(() -> new BusinessException("用户不存在"));
        user.setAvatar(userUpdateAvatarBo.getAvatar());
        userMapper.updateById(user);
    }

    /**
     * 修改用户信息
     *
     * @param userUpdateInfoBo
     */
    @Override
    public void updateUserInfo(UserUpdateInfoBo userUpdateInfoBo) {
        User user = userMapper.findById(userUpdateInfoBo.getCurrentUserUuid())
            .orElseThrow(() -> new BusinessException("用户不存在"));
        user.setSex(userUpdateInfoBo.getSex());
        user.setSignature(userUpdateInfoBo.getSignature());
        user.setBirthday(userUpdateInfoBo.getBirthday());
        userMapper.updateById(user);
    }

    /**
     * 根据用户uuid获取用户信息
     *
     * @param userUuid
     * @return
     */
    @Override
    public UserInfoVo getByUserUuid(String userUuid) {
        User user = userMapper.findById(userUuid)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        return BeanUtil.copyProperties(user, UserInfoVo.class);
    }

    /**
     * QQ回调登录
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    public void loginByQQ(HttpServletRequest request, HttpServletResponse response)
        throws QQConnectException, IOException, NoSuchAlgorithmException {
        AccessToken accessTokenObj = new Oauth().getAccessTokenByRequest(request);
        if (ObjectUtil.isEmpty(accessTokenObj.getAccessToken())) {
            log.error("QQ登录失败，没有响应参数");
            throw new BusinessException("QQ登录失败");
        }
        String accessToken = accessTokenObj.getAccessToken();
        // 获取openId
        OpenID openIDObj = new OpenID(accessToken);
        String openId = openIDObj.getUserOpenID();
        // 获取用户信息
        UserInfo qZoneUserInfo = new UserInfo(accessToken, openId);
        UserInfoBean userInfo = qZoneUserInfo.getUserInfo();
        if (userInfo.getRet() == 0) {
            // 用户昵称
            String nickName = userInfo.getNickname();
            // 用户性别
            String sex = userInfo.getGender();
            // 用户头像
            String avatar = userInfo.getAvatar().getAvatarURL50();
            // 先判断该用户是否已通过QQ登录
            UserQQ userQQ = userQQMapper.selectOneByColumn("open_id", openId);
            if (ObjectUtil.isNotEmpty(userQQ)) {
                // 直接登录
                User user = userMapper.selectById(userQQ.getUserUuid());
                // 修改用户信息
                user.setAvatar(avatar);
                user.setSex("男".equals(sex) ? 1 : 2);
                userMapper.updateById(user);
                response.sendRedirect("http://startcampus.cn/qq/AUTH_TOKEN=" + SecurityUtils
                    .genToken(user.getUuid(), user.getPassword()));
            }
            // 不存在，首先保存用户信息
            // 设置默认密码为123456
            String salt = CodecUtils.generateSalt();
            User user = User.builder()
                .uuid(UUIDGenerator.getUUID())
                .avatar(avatar)
                .password(CodecUtils.md5Hex(salt, "123456"))
                .salt(salt)
                .nickName(nickName)
                .sex("男".equals(sex) ? 1 : 2)
                .sourceType(UserSourceType.QQ).build();
            userMapper.insert(user);
            // 保存用户QQ信息
            userQQ = UserQQ.builder()
                .uuid(UUIDGenerator.getUUID())
                .userUuid(user.getUuid())
                .openId(openId).build();
            userQQMapper.insert(userQQ);
            response.sendRedirect("http://startcampus.cn/qq/AUTH_TOKEN=" + SecurityUtils
                .genToken(user.getUuid(), user.getPassword()));
        } else {
            log.error("QQ登录失败，未能获取到用户信息，原因是：{}", userInfo.getMsg());
            throw new BusinessException("QQ登录失败");
        }
    }

    /**
     * 设置基本信息
     *
     * @param setBasicMessageBo
     */
    @Override
    public void setBasicMessage(SetBasicMessageBo setBasicMessageBo) {
        User user = userMapper.findById(setBasicMessageBo.getCurrentUserUuid())
            .orElseThrow(() -> new BusinessException("用户不存在"));
        // 判断昵称是否被使用(已设置基本信息的)
        if (userMapper.existsNickNameAndSetBasicMessage(setBasicMessageBo.getNickName())) {
            throw new BusinessException("该昵称已被使用");
        }
        user.setNickName(setBasicMessageBo.getNickName());
        user.setSex(setBasicMessageBo.getSex());
        user.setSignature(setBasicMessageBo.getSignature());
        user.setAvatar(setBasicMessageBo.getAvatar());
        user.setBirthday(setBasicMessageBo.getBirthday());
        // 设置为已设置基本信息
        user.setIsSetBasicMessage(true);
        userMapper.updateById(user);
        School school = schoolMapper.findById(setBasicMessageBo.getSchoolUuid())
            .orElseThrow(() -> new BusinessException("学校不存在"));
        if (school.getDisabled()) {
            throw new BusinessException("学校已被禁用");
        }
        UserSchool userSchool = UserSchool.builder()
            .uuid(UUIDGenerator.getUUID())
            .userUuid(user.getUuid())
            .schoolUuid(school.getUuid()).build();
        userSchoolMapper.insert(userSchool);
    }

    /**
     * 修改密码（返回token）
     *
     * @param updatePasswordBo
     */
    @Override
    public String updatePassword(UpdatePasswordBo updatePasswordBo)
        throws UnsupportedEncodingException, NoSuchAlgorithmException {
        User user = userMapper.selectById(updatePasswordBo.getCurrentUserUuid());
        // 判断旧密码是否相同
        String oldPasswordWithSalt = CodecUtils
            .md5Hex(user.getSalt(), updatePasswordBo.getOldPassword());
        if (!user.getPassword().equals(oldPasswordWithSalt)) {
            throw new BusinessException("旧密码错误");
        }
        // 判断密码是否包含数字和字母
        String pattern = "^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]{6,20})$";
        if (!Pattern.compile(pattern).matcher(updatePasswordBo.getNewPassword()).matches()) {
            throw new BusinessException("新密码必须包含字母和数字");
        }
        // 判断两次密码是否一致
        if (!updatePasswordBo.getNewPassword().equals(updatePasswordBo.getRePassword())) {
            throw new BusinessException("两次密码不一致");
        }
        // 重新生成盐
        String salt = CodecUtils.generateSalt();
        String password = CodecUtils.md5Hex(salt, updatePasswordBo.getNewPassword());
        user.setSalt(salt);
        user.setPassword(password);
        userMapper.updateById(user);
        // 重新生成token信息并返回
        return SecurityUtils.genToken(user.getUuid(), user.getPassword());
    }

    /**
     * 关注用户
     *
     * @param uuid             被关注用户uuid
     * @param currentLoginUuid 关注用户uuid
     */
    @Override
    public void follow(String uuid, String currentLoginUuid) {
        if (uuid.equals(currentLoginUuid)) {
            throw new BusinessException("不能关注自己哦");
        }
        User user = userMapper.findById(uuid)
            .orElseThrow(() -> new BusinessException("被关注用户不存在"));
        User followUser = userMapper.selectById(currentLoginUuid);
        if (user.getDisabled()) {
            throw new BusinessException("被关注用户已被禁用");
        }
        if (!user.getIsSetBasicMessage()) {
            throw new BusinessException("被关注用户未设置基本信息");
        }
        // 判断是否已经关注
        if (userFollowMapper.isFollow(uuid, currentLoginUuid)) {
            throw new BusinessException("不能重复关注哦");
        }
        UserFollow userFollow = UserFollow.builder()
            .uuid(UUIDGenerator.getUUID())
            .followUserUuid(currentLoginUuid)
            .followedUserUuid(uuid)
            .build();
        userFollowMapper.insert(userFollow);
        // mq 发送消息
        SystemMessageMqParamModel systemMessageMqParamModel = new SystemMessageMqParamModel();
        systemMessageMqParamModel.setMessageType(SystemMessageType.FOLLOW_USER);
        systemMessageMqParamModel.setReceiveUserUuid(uuid);
        systemMessageMqParamModel.setContent(followUser.getNickName() + "关注了你");
        mqSendMessageUtil.sendSystemMessage(JSONUtil.toJsonStr(systemMessageMqParamModel));
    }

    /**
     * 取消关注用户
     *
     * @param uuid             被关注用户uuid
     * @param currentLoginUuid 关注用户uuid
     */
    @Override
    public void cancelFollow(String uuid, String currentLoginUuid) {
        if (uuid.equals(currentLoginUuid)) {
            throw new BusinessException("不能取消关注自己哦");
        }
        User user = userMapper.findById(uuid)
            .orElseThrow(() -> new BusinessException("被关注用户不存在"));
        if (user.getDisabled()) {
            throw new BusinessException("被关注用户已被禁用");
        }
        if (!user.getIsSetBasicMessage()) {
            throw new BusinessException("被关注用户未设置基本信息");
        }
        userFollowMapper.cancelFollow(uuid, currentLoginUuid);
    }

    /**
     * 是否关注
     *
     * @param uuid             被关注用户uuid
     * @param currentLoginUuid 关注用户uuid
     * @return
     */
    @Override
    public Boolean isFollow(String uuid, String currentLoginUuid) {
        return userFollowMapper.isFollow(uuid, currentLoginUuid);
    }

    /**
     * 分页查询关注用户
     *
     * @param userFollowPageSearchBo
     * @return
     */
    @Override
    public IPage<UserFollowVo> pageFollow(UserFollowPageSearchBo userFollowPageSearchBo) {
        UserFollowPageSearchQo userFollowPageSearchQo = BeanUtil
            .copyProperties(userFollowPageSearchBo, UserFollowPageSearchQo.class);
        IPage<UserFollow> userFollowIPage = userFollowMapper.pageFollow(
            new Page<UserFollow>(userFollowPageSearchBo.getPage(),
                userFollowPageSearchBo.getSize()), userFollowPageSearchQo);
        List<UserFollowVo> userFollowVos;
        IPage<UserFollowVo> userFollowVoIPage = new Page<>();
        userFollowVos = userFollowIPage.getRecords().stream().map(userFollow -> {
            UserFollowVo userFollowVo = BeanUtil.copyProperties(userFollow, UserFollowVo.class);
            userFollowVo.setFollowedUserUuid(userFollow.getFollowedUser().getUuid());
            userFollowVo.setFollowedUserNickName(userFollow.getFollowedUser().getNickName());
            userFollowVo.setFollowedUserAvatar(userFollow.getFollowedUser().getAvatar());
            userFollowVo.setFollowedUserSignature(userFollow.getFollowedUser().getSignature());
            return userFollowVo;
        }).collect(Collectors.toList());
        userFollowVoIPage.setRecords(userFollowVos);
        return userFollowVoIPage;
    }

    /**
     * 修改背景图
     *
     * @param userUpdateBackgroundImageBo
     */
    @Override
    public void updateBackgroundImage(UserUpdateBackgroundImageBo userUpdateBackgroundImageBo) {
        User user = userMapper.selectById(userUpdateBackgroundImageBo.getCurrentUserUuid());
        user.setBackgroundImage(userUpdateBackgroundImageBo.getBackgroundImage());
        userMapper.updateById(user);
    }

    /**
     * 修改手机号
     *
     * @param updatePhoneBo
     */
    @Override
    public void updatePhone(UpdatePhoneBo updatePhoneBo) {
        // 设置redisTemplate
        redisUtil.setRedisTemplate(smsRedisTemplate);
        // 校验验证码
        if (!redisUtil.isExist(updateCodeKey + updatePhoneBo.getNewPhone())) {
            throw new BusinessException("验证码已失效");
        }
        String code = redisUtil.getStringValue(updateCodeKey + updatePhoneBo.getNewPhone());
        if (!code.equals(updatePhoneBo.getCode())) {
            throw new BusinessException("验证码错误");
        }
        User user = userMapper.selectById(updatePhoneBo.getCurrentUserUuid());
        // 判断是否修改了手机号
        if (ObjectUtil.isNotEmpty(user.getPhone()) && !user.getPhone()
            .equals(updatePhoneBo.getNewPhone())) {
            if (!user.getPhone().equals(updatePhoneBo.getNewPhone())) {
                // 手机号不一致
                // 判断新手机号是否被注册
                if (userMapper.existsByColumn("phone", updatePhoneBo.getNewPhone())) {
                    throw new BusinessException("该手机号已被注册");
                }
                // 设置新的手机号
                user.setPhone(updatePhoneBo.getNewPhone());
                // 设置新的ip地址
                MobileLocalModel mobileLocal = tianXinData.getMobileLocal(user.getPhone());
                if (ObjectUtil.isNotEmpty(mobileLocal)) {
                    user.setIpaddress(mobileLocal.getMobileprovice() + mobileLocal.getMobilearea());
                }
                userMapper.updateById(user);
            }
            // 手机号一致，不做任何处理
        } else {
            // 不存在手机号也要校验该手机号是否被注册
            if (userMapper.existsByColumn("phone", updatePhoneBo.getNewPhone())) {
                throw new BusinessException("该手机号已被注册");
            }
        }
        // 删除验证码
        redisUtil.delete(updateCodeKey + updatePhoneBo.getNewPhone());
    }

    /**
     * 密码登录
     *
     * @param userLoginBo
     */
    @Override
    public String loginByPassword(UserLoginBo userLoginBo)
        throws UnsupportedEncodingException, NoSuchAlgorithmException {
        // 通过手机号获取信息
        User user = userMapper.findByColumn("phone", userLoginBo.getPhone())
            .orElseThrow(() -> new BusinessException("手机号或密码错误"));
        // 判断密码是否正确
        if (!user.getPassword()
            .equals(CodecUtils.md5Hex(user.getSalt(), userLoginBo.getPassword()))) {
            throw new BusinessException("手机号或密码错误");
        }
        // 密码手机号都正确，登录成功
        return SecurityUtils.genToken(user.getUuid(), user.getPassword());
    }
}
